using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using Emerald;
using KaroInterface;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Action = Emerald.Action;
using ButtonState = Microsoft.Xna.Framework.Input.ButtonState;
using Game = Emerald.Game;
using Keys = Microsoft.Xna.Framework.Input.Keys;

namespace XNAProject
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class XnaGame : Microsoft.Xna.Framework.Game
    {
        public GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        // Menu
        public Menu menu;

        //Textures
        private Texture2D background;
        private Texture2D tileTexture;

        // input states
        KeyboardState previousKeyState = Keyboard.GetState();
        KeyboardState currentKeyState = Keyboard.GetState();
        MouseState previousMouseState = Mouse.GetState();
        private int viewingType = 0;

        // framerate
        private double deltaFpsTime = 0;
        private double currentFramerate = 0;
        private double deltaFpsTimeDraw = 0;
        private double currentFramerateDraw = 0;
        private double _targetFramerate;
        private double TargetFramerate
        {
            get { return _targetFramerate; }
            set
            {
                if (value <= 0 || value > 1000)
                    return;
                _targetFramerate = value;
                TargetElapsedTime = TimeSpan.FromMilliseconds(1000 / _targetFramerate);
            }
        }

        // rotating
        private Camera camera;

        //sound effects
        private SoundEffect error;

        // models
        private BoundingBoxModel tileModel;
        private BoundingBoxModel pawnRedModel;
        private BoundingBoxModel pawnWhiteModel;
        private Model torus;
        private bool torusShow;
        float torusRotation = 0;
        private Model sphere;
        float sphereScale = 0;
        bool sphereScaleUp = true;
        private Model textModel;

        // debug
        private SpriteFont debugFont;
        private string[] stringColumns = new string[5];
        private readonly string endl = System.Environment.NewLine;
        private bool showBoundingBoxes = false;
        private bool showDebugInformation = false;

        public Game game;
        private List<BoardPoint> listBoardPoints;

        private const float TILE_HEIGHT = 0.15f;
        private const float PIECE_HEIGHT = 0.5f;

        private bool showAllCoordinates = false;
        private bool showSideCoordinates = true;

        private bool isBoardTileSelected = false;
        private int timePassedForAction = 0;

        private bool isMouseClicked = false;
        private Tile topTile;
        private Tile bottomTile;

        public int[] minRes = new int[] { int.MinValue, int.MinValue };

        public bool ActivateCamera
        {
            get { return camera.ReadInput; }
            set { camera.ReadInput = value; }
        }

        public GameTime GameTime { get; set; }


        public XnaGame()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            IsMouseVisible = true;
            Window.Title = "Karo Emerald";
            TargetFramerate = 40;
            graphics.PreparingDeviceSettings += new EventHandler<PreparingDeviceSettingsEventArgs>(graphics_PreparingDeviceSettings);

            //startNewGame(new Game(new AI(EColor.Red), new HumanPlayer(EColor.White), EColor.White));

#if DEBUG
            showDebugInformation = true;
#endif

        }

        void graphics_PreparingDeviceSettings(object sender, PreparingDeviceSettingsEventArgs e)
        {
            bool resOk = false;

            foreach (DisplayMode dm in GraphicsAdapter.DefaultAdapter.SupportedDisplayModes)
            {
                if (minRes[0] <= dm.Width && minRes[1] <= dm.Height)
                {
                    minRes[0] = dm.Width;
                    minRes[1] = dm.Height;
                    resOk = true;
                }
            }

#if DEBUG
            minRes[0] = 800;
            minRes[1] = 600;
            resOk = true;
#endif

            if (resOk)
            {
                e.GraphicsDeviceInformation.PresentationParameters.BackBufferWidth = minRes[0];
                e.GraphicsDeviceInformation.PresentationParameters.BackBufferHeight = minRes[1];
            }
            else
            {
                global::System.Windows.Forms.MessageBox.Show("Resolution not supported", "Error", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error);
                this.Exit();
            }

        }

        public void startNewGame(Game _game)
        {
            game = _game;

            game.PlayerChanged += game_PlayerChanged;
            game.BoardChanged += game_BoardChanged;
            game.GameEnded += game_GameEnded;

            GameIsOver = false;
            MouseIsActive = true;

            animationInProgress = false;

            selectedPiece = null;
            SelectedTile = null;

            selectedBoardPoint = default(BoardPoint);
            SelectedBoardpoint = default(BoardPoint);

            handledMouseAction = null;

            resetBoundingBoxes();
        }

        void game_GameEnded(object sender, EColor winningPlayer)
        {
            GameIsOver = true;
            winMessage = winningPlayer + " won";
            winMessageColor = getColor(winningPlayer);
        }

        void game_BoardChanged(object sender, BoardPoint removed, BoardPoint added)
        {
            resetBoundingBoxes();
        }

        void game_PlayerChanged(object sender, EColor newPlayer)
        {
            if (!GameIsOver && !(game.PlayerToPlay is HumanPlayer))
            {
                MouseIsActive = false;
                Thread t = new Thread(
                    delegate()
                    {
                        torusShow = true;
                        AI p = (AI)game.PlayerToPlay;
                        Action bestAction = new Action(p.DoTurn(game.GameState.DataString));

                        while (animationInProgress)
                        {
                            Thread.Sleep(this.TargetElapsedTime);
                        }

                        bool pa = game.GameState.PerformAction(bestAction);
                        handledMouseAction = bestAction;

                        animationInProgress = true;
                        animationStart = GameTime.TotalGameTime.TotalMilliseconds;

                        torusShow = false;
                    });
                t.Start();
            }
            else
            {
                MouseIsActive = true;
            }
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            camera = new Camera(new Vector3(0, 7, 7), new Vector3(0, TILE_HEIGHT, 0), this);

            menu = new Menu(this);

            listBoardPoints = new List<BoardPoint>();

            Components.Add(camera);
            Components.Add(menu);


            graphics.PreferredBackBufferWidth = minRes[0];
            graphics.PreferredBackBufferHeight = minRes[1];

#if !DEBUG
            graphics.ToggleFullScreen();
#endif

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            tileModel = new BoundingBoxModel(Content.Load<Model>("Models/tile"), GraphicsDevice);
            pawnRedModel = new BoundingBoxModel(Content.Load<Model>("Models/pawnRed"), GraphicsDevice);
            pawnWhiteModel = new BoundingBoxModel(Content.Load<Model>("Models/pawnWhite"), GraphicsDevice);

            textModel = Content.Load<Model>("Models/text");

            torus = Content.Load<Model>("Models/Torus");
            sphere = Content.Load<Model>("Models/sphere");

            error = Content.Load<SoundEffect>("Sounds/error");

            debugFont = Content.Load<SpriteFont>("Fonts/Debugfont");

            background = Content.Load<Texture2D>("Graphics/bg2");
            tileTexture = Content.Load<Texture2D>("Graphics/stucco4");
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            Content.Unload();
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            GameTime = gameTime;
            if (!menu.IsVisible)
            {
                #region Updating framerate

                // framerate
                // The time since Update() method was last called.
                double elapsed = gameTime.ElapsedGameTime.TotalMilliseconds;

                // Ads the elapsed time to the cumulative delta time.
                this.deltaFpsTime += elapsed;

                // If delta time is greater than a second: (a) the framerate is calculated, (b) it is marked to be drawn, and (c) the delta time is adjusted, accordingly.
                if (this.deltaFpsTime > 1000)
                {
                    this.currentFramerate = (1000.0 / elapsed);
                    this.deltaFpsTime -= 1000;
                }

                #endregion

                #region Updating torus rotation & sphere scaling

                torusRotation += 1.5f;
                if (torusRotation > 360) torusRotation -= 360;

                if (sphereScaleUp)
                    sphereScale += 0.005f;
                else
                    sphereScale -= 0.005f;

                if (sphereScale > 0.4f) sphereScaleUp = false;
                if (sphereScale < 0.2f) sphereScaleUp = true;

                #endregion

                currentKeyState = Keyboard.GetState();
                MouseState currentMouseState = Mouse.GetState();

                #region ViewTypes on Board marking

                if (isKeyPressed(Keys.Z))
                {
                    viewingType++;
                    if (viewingType > 3) viewingType = 0;

                    switch (viewingType)
                    {
                        case 0:
                            showAllCoordinates = false;
                            showSideCoordinates = false;
                            break;
                        case 1:
                            showAllCoordinates = false;
                            showSideCoordinates = true;
                            break;
                        case 2:
                            showAllCoordinates = true;
                            showSideCoordinates = true;
                            break;
                        case 3:
                            showAllCoordinates = true;
                            showSideCoordinates = false;
                            break;
                    }
                }

                #endregion

                if (this.IsActive)
                {
                    #region Keypressses

                    // Allows the game to exit
                    if (isKeyPressed(Keys.Escape))
                    {
                        camera.ReadInput = false;
                        menu.IsVisible = true;
                    }

                    if (isKeyPressed(Keys.B))
                    {
                        showBoundingBoxes = !showBoundingBoxes;
                    }

                    if (isKeyPressed(Keys.D))
                    {
                        showDebugInformation = !showDebugInformation;
                    }

                    if (isKeyPressed(Keys.M))
                    {
                        if (graphics.IsFullScreen)
                        {
                            graphics.PreferredBackBufferWidth = 800;
                            graphics.PreferredBackBufferHeight = 600;
                        }
                        else
                        {
                            graphics.PreferredBackBufferWidth = minRes[0];
                            graphics.PreferredBackBufferHeight = minRes[1];
                        }

                        graphics.ApplyChanges();
                        graphics.ToggleFullScreen();
                    }

                    if (isKeyPressed(Keys.H))
                    {
                        IAction bestMove = MiniMax.MiniMaxDecision(game.GameState, game.ColorToPlay);
                        MessageBox.Show(bestMove.ToString());
                    }

                    if (isKeyPushed(Keys.F))
                        TargetFramerate--;
                    if (isKeyPushed(Keys.G))
                        TargetFramerate++;

                    #endregion

                    #region Mouse handling

                    timePassedForAction += gameTime.ElapsedGameTime.Milliseconds;
                    if (isKeyPressed(currentMouseState.LeftButton, previousMouseState.LeftButton) && MouseIsActive)
                    {
                        isMouseClicked = true;

                        if (selectedPiece != null)
                        {
                            isBoardTileSelected = true;
                            timePassedForAction = 0;
                        }
                        else
                        {
                            isBoardTileSelected = false;
                        }

                        SelectedTile = FirstIntersectionWith();
                        if (SelectedTile == null)
                            SelectedBoardpoint = FirstIntersectionWithNullTiles();

                        if (SelectedTile != null)
                        {
                            if (SelectedTile.Piece != null &&
                                (SelectedTile.Piece.Color != game.ColorToPlay ||
                                 (SelectedTile.Piece.Color == game.ColorToPlay && selectedPiece != null)))
                            {
                                SelectedTile = null;
                                selectedPiece = null;
                                SelectedBoardpoint = default(BoardPoint);
                                error.Play();
                            }
                        }
                    }
                    if ((timePassedForAction > 1000 || SelectedBoardpoint != default(BoardPoint)) && isMouseClicked)
                    {
                        handleMouseClick(gameTime);
                        isMouseClicked = false;
                    }


                    if (isKeyPressed(currentMouseState.RightButton, previousMouseState.RightButton) && MouseIsActive)
                    {
                        SelectedTile = null;
                        selectedPiece = null;
                    }

                    #endregion
                }

                #region Update Debug information

                stringColumns[0] = string.Format("Rotation (degrees): {0}{1}Rotation speed:{2}{1}{1}Current fixed: {3}{1}Switching to fixed?: {4}{1}{1}Rotation quadrant:{5}", camera.RotationY, endl, camera.FixedDegrees, camera.CurrentFixed, camera.SwitchToFixed, camera.RotationQuadrant);

                stringColumns[3] = string.Format("Tiles count: {0}{1}Pieces count: {2}{1}Color to play: {3}", game.GameState.Board.BoardArray.Tiles.Count(), endl, game.GameState.Board.BoardArray.Pieces.Count(p => p != null), game.GameState.ColorToPlay);

                StringBuilder pressedKeys = new StringBuilder();
                foreach (Keys key in Keyboard.GetState().GetPressedKeys())
                {
                    if (pressedKeys.Length > 0)
                        pressedKeys.Append(", ");
                    pressedKeys.Append(key);
                }
                stringColumns[4] = string.Format("Key pressed: {0}{1}", pressedKeys, endl);

                if (showBoundingBoxes)
                    stringColumns[4] += string.Format("{1}Amount of 'null' bboxes: {0}{1}", listBoardPoints.Count, endl);

                if (SelectedTile != null)
                {
                    stringColumns[2] = string.Format("Selected Tile: {0}, {1}{2}", ((char)(SelectedTile.Location.X + 65)), SelectedTile.Location.Y, endl);

                    if (SelectedTile.Piece != null)
                    {
                        stringColumns[2] += string.Format("Piece color: {0}{1}Piece state: {2}", SelectedTile.Piece.Color, endl, SelectedTile.Piece.State);
                    }
                }
                else
                {
                    stringColumns[2] = "Selected Tile: None";
                }

                stringColumns[1] = string.Format("Current FPS (update): {0}{1}Current FPS (draw): {2}{1}Target FPS: {3}", Math.Round(currentFramerate, 2), endl, Math.Round(currentFramerateDraw, 2), Math.Round(TargetFramerate));

                #endregion

                previousKeyState = currentKeyState;
                previousMouseState = currentMouseState;


                base.Update(gameTime);
            }
            else
            {
                base.Update(gameTime);
                return;
            }


        }

        Tile SelectedTile { get; set; }
        BoardPoint SelectedBoardpoint { get; set; }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            if (!menu.IsVisible)
            {

                #region Drawing Framerate

                GraphicsDevice.RenderState.MultiSampleAntiAlias = true;
                // framerate
                // The time since Update() method was last called.
                double elapsed = gameTime.ElapsedGameTime.TotalMilliseconds;

                // Ads the elapsed time to the cumulative delta time.
                this.deltaFpsTimeDraw += elapsed;

                // If delta time is greater than a second: (a) the framerate is calculated, (b) it is marked to be drawn, and (c) the delta time is adjusted, accordingly.
                if (this.deltaFpsTimeDraw > 1000)
                {
                    this.currentFramerateDraw = (1000.0 / elapsed);
                    this.deltaFpsTimeDraw -= 1000;
                }

                #endregion

                GraphicsDevice.Clear(Color.CornflowerBlue);

                #region Draw background

                spriteBatch.Begin();
                spriteBatch.Draw(background,
                                 new Rectangle(0, 0, graphics.PreferredBackBufferWidth,
                                               graphics.PreferredBackBufferHeight), Color.White);
                spriteBatch.End();
                resetAfterSpriteBatch();

                #endregion

                #region Draw message about winning

                if (GameIsOver)
                {
                    Draw3DText(winMessage, new Vector3(0, 1.4f, 0), winMessageColor);
                    camera.RotateYRelative(-2);
                }

                #endregion

                #region Draw Tiles & Pieces & characters

                bottomTile = new Tile(byte.MinValue, byte.MinValue);
                topTile = new Tile(byte.MaxValue, byte.MaxValue);

                foreach (Tile tile in game.GameState.Board.BoardArray.Tiles)
                {
                    foreach (ModelMesh modelMesh in tileModel.Model.Meshes)
                    {
                        foreach (BasicEffect effect in modelMesh.Effects)
                        {
                            effect.EnableDefaultLighting();

                            if (tile.Equals(SelectedTile) && isBoardTileSelected)
                            {
                                effect.DiffuseColor = Color.Yellow.ToVector3();
                            }
                            else
                            {
                                effect.DiffuseColor = Color.Gray.ToVector3();
                            }

                            if (selectedPiece != null && isBoardTileSelected)
                            {
                                if (tile.Location == selectedPiece.Location)
                                {
                                    effect.DiffuseColor = Color.Yellow.ToVector3();
                                }
                            }
                            effect.DirectionalLight0.Enabled = true;
                            effect.DirectionalLight0.DiffuseColor = Color.Black.ToVector3();

                            effect.World = getTranslationMatrix(tile);
                            effect.View = camera.Rotation * camera.View;
                            effect.Projection = camera.Projection;
                            effect.TextureEnabled = true;
                            effect.Texture = tileTexture;
                        }
                        modelMesh.Draw();
                    }

                    if (showBoundingBoxes)
                    {
                        tileModel.DrawBoundingBox(camera, getTranslationMatrix(tile));
                    }

                    if (tile.Piece != null)
                    {
                        BoundingBoxModel pieceModel = getPieceModel(tile.Piece.Color);
                        Vector3 currentColor = getColor(tile.Piece.Color);


                        Matrix world = Matrix.Identity;
                        foreach (ModelMesh modelMesh in pieceModel.Model.Meshes)
                        {
                            foreach (BasicEffect effect in modelMesh.Effects)
                            {

                                effect.EnableDefaultLighting();
                                if (tile.Equals(SelectedTile))
                                {
                                    effect.DiffuseColor = Color.Yellow.ToVector3();
                                }
                                else
                                {
                                    effect.DiffuseColor = currentColor;
                                }
                                world = getWorldForPieceOnTile(tile, gameTime);
                                effect.World = world;
                                effect.View = camera.Rotation * camera.View;
                                effect.Projection = camera.Projection;
                            }
                            modelMesh.Draw();
                        }

                        if (showBoundingBoxes)
                            pieceModel.DrawBoundingBox(camera, world);

                    }

                    if (showAllCoordinates)
                    {
                        float i = tile.Location.X;
                        float j = tile.Location.Y;

                        BoardPoint topLeft = game.GameState.Board.BoardArray.TopLeftBoundary;
                        BoardPoint bottomRight = game.GameState.Board.BoardArray.BottomRightBoundary;

                        GraphicsDevice.RenderState.CullMode = CullMode.None;

                        ModelMesh modelMeshChar = textModel.Meshes[Convert.ToString((char)(65 + i))];

                        float y = 0 - ((bottomRight.Y - topLeft.Y) / 2F) + (j - topLeft.Y);
                        float x = 0 - ((bottomRight.X - topLeft.X) / 2F) + (i - topLeft.X);
                        DrawCharOnPoint(modelMeshChar, new Vector3(x - 0.2F, 1, y), Color.Green.ToVector3());

                        modelMeshChar = textModel.Meshes[Convert.ToString(j)];
                        DrawCharOnPoint(modelMeshChar, new Vector3(x + 0.2F, 1, y), Color.Blue.ToVector3());
                    }

                    if (showBoundingBoxes)
                    {
                        for (int i = 0; i < listBoardPoints.Count; i++)
                        {
                            tileModel.DrawBoundingBox(camera, getTranslationMatrix(listBoardPoints[i]));
                        }
                    }
                    if (SelectedBoardpoint != null && SelectedBoardpoint != default(BoardPoint))
                    {
                        foreach (ModelMesh modelMesh in tileModel.Model.Meshes)
                        {
                            foreach (BasicEffect effect in modelMesh.Effects)
                            {
                                effect.EnableDefaultLighting();
                                effect.DiffuseColor = Color.LightGray.ToVector3();

                                effect.World = getTranslationMatrix(SelectedBoardpoint);
                                effect.View = camera.Rotation * camera.View;
                                effect.Projection = camera.Projection;
                                effect.TextureEnabled = true;
                                effect.Texture = tileTexture;
                            }
                            modelMesh.Draw();
                        }
                    }

                    if (tile.Location.Y < topTile.Location.Y)
                        topTile = tile;
                    if (tile.Location.Y > bottomTile.Location.Y)
                        bottomTile = tile;
                }

                #endregion

                #region Draw side letters and numbers

                if (showSideCoordinates)
                {
                    GraphicsDevice.RenderState.CullMode = CullMode.None;

                    BoardPoint topLeft = game.GameState.Board.BoardArray.TopLeftBoundary;
                    BoardPoint bottomRight = game.GameState.Board.BoardArray.BottomRightBoundary;

                    for (int i = topLeft.X - 1; i <= bottomRight.X + 1; i++)
                    {
                        ModelMesh modelMeshChar = textModel.Meshes[Convert.ToString((char)(65 + i))];

                        DrawCharOnPoint(modelMeshChar,
                                        new Vector3(0 - ((bottomRight.X - topLeft.X) / 2F) + (i - topLeft.X) - 0.2F, 0.05F,
                                                    0 - ((bottomRight.Y - topLeft.Y) / 2F) - 2F), Color.Green.ToVector3());
                        DrawCharOnPoint(modelMeshChar,
                                        new Vector3(0 - ((bottomRight.X - topLeft.X) / 2F) + (i - topLeft.X) - 0.2F, 0.05F,
                                                    0 + ((bottomRight.Y - topLeft.Y) / 2F) + 2F), Color.Green.ToVector3());
                    }

                    for (int j = bottomRight.Y + 1, h = topTile.Location.Y - 1; j >= topLeft.Y - 1; j--, h++)
                    {
                        ModelMesh modelMeshChar = textModel.Meshes[Convert.ToString(h)];

                        DrawCharOnPoint(modelMeshChar,
                                        new Vector3(0 - ((bottomRight.X - topLeft.X) / 2F) - 2F, 0.05F,
                                                    0 + ((bottomRight.Y - topLeft.Y) / 2F) - (j - topLeft.Y)),
                                        Color.Blue.ToVector3());
                        DrawCharOnPoint(modelMeshChar,
                                        new Vector3(0 + ((bottomRight.X - topLeft.X) / 2F) + 2F, 0.05F,
                                                    0 + ((bottomRight.Y - topLeft.Y) / 2F) - (j - topLeft.Y)),
                                        Color.Blue.ToVector3());

                    }
                }

                #endregion

                #region Draw debug information

                if (showDebugInformation)
                {
                    spriteBatch.Begin();

                    int columnCount = 0;
                    foreach (string s in stringColumns)
                    {
                        if (s != null)
                        {
                            spriteBatch.DrawString(debugFont, s, new Vector2(12 + columnCount * 160 + 1, 12 + 1), Color.Black);
                            spriteBatch.DrawString(debugFont, s, new Vector2(12 + columnCount * 160, 12), Color.White);
                        }
                        columnCount++;
                    }

                    spriteBatch.End();
                    resetAfterSpriteBatch();
                }

                #endregion

                #region Draw torus when AI is thinking

                if (torusShow)
                {

                    foreach (ModelMesh modelMesh in torus.Meshes)
                    {
                        foreach (BasicEffect effect in modelMesh.Effects)
                        {
                            effect.EnableDefaultLighting();
                            effect.DiffuseColor = Color.White.ToVector3();
                            effect.World = Matrix.Identity * Matrix.CreateScale(0.6f) *
                                           Matrix.CreateRotationY(MathHelper.ToRadians(torusRotation)) *
                                           Matrix.CreateTranslation(0, 1.5f, 0);
                            effect.View = camera.Rotation * camera.View;
                            effect.Projection = camera.Projection;
                        }
                        modelMesh.Draw();
                    }

                    foreach (ModelMesh modelMesh in torus.Meshes)
                    {
                        foreach (BasicEffect effect in modelMesh.Effects)
                        {
                            effect.EnableDefaultLighting();
                            effect.DiffuseColor = Color.Red.ToVector3();
                            effect.World = Matrix.Identity * Matrix.CreateScale(0.5f) *
                                           Matrix.CreateRotationX(MathHelper.ToRadians(torusRotation)) *
                                           Matrix.CreateTranslation(0, 1.5f, 0);
                            effect.View = camera.Rotation * camera.View;
                            effect.Projection = camera.Projection;
                        }
                        modelMesh.Draw();
                    }

                    foreach (ModelMesh modelMesh in sphere.Meshes)
                    {
                        foreach (BasicEffect effect in modelMesh.Effects)
                        {
                            effect.EnableDefaultLighting();
                            effect.DirectionalLight0.Enabled = true;
                            effect.DirectionalLight0.DiffuseColor = Color.Black.ToVector3();
                            effect.World = Matrix.Identity * Matrix.CreateScale(sphereScale) *
                                           Matrix.CreateRotationZ(MathHelper.ToRadians(torusRotation)) *
                                           Matrix.CreateRotationY(MathHelper.ToRadians(-torusRotation)) *
                                           Matrix.CreateRotationX(MathHelper.ToRadians(torusRotation)) *
                                           Matrix.CreateTranslation(0, 1.5f, 0);
                            effect.View = camera.Rotation * camera.View;
                            effect.Projection = camera.Projection;
                            effect.TextureEnabled = true;
                            effect.Texture = tileTexture;
                        }
                        modelMesh.Draw();
                    }
                }

                #endregion
            }
            base.Draw(gameTime);
        }

        private void resetBoundingBoxes()
        {
            listBoardPoints = new List<BoardPoint>();
            foreach (Tile tile in game.GameState.Board.BoardArray.Tiles)
            {
                BoardPoint top, left, right, bottom;
                top = new BoardPoint((byte)(tile.Location.X - 1), tile.Location.Y);
                left = new BoardPoint(tile.Location.X, (byte)(tile.Location.Y - 1));
                right = new BoardPoint(tile.Location.X, (byte)(tile.Location.Y + 1));
                bottom = new BoardPoint((byte)(tile.Location.X + 1), tile.Location.Y);
                if (!listBoardPoints.Contains(top) && game.GameState.Board.BoardArray[top] == null) listBoardPoints.Add(top);
                if (!listBoardPoints.Contains(left) && game.GameState.Board.BoardArray[left] == null) listBoardPoints.Add(left);
                if (!listBoardPoints.Contains(right) && game.GameState.Board.BoardArray[right] == null) listBoardPoints.Add(right);
                if (!listBoardPoints.Contains(bottom) && game.GameState.Board.BoardArray[bottom] == null) listBoardPoints.Add(bottom);
            }
        }

        private void Draw3DText(String text, Vector3 position, Vector3 color)
        {
            GraphicsDevice.RenderState.CullMode = CullMode.None;
            char[] textArray = text.ToCharArray();
            for (int l = 0; l < textArray.Length; l++)
            {
                char c = textArray[l];
                string s = c.ToString().ToUpper();
                if (textModel.Meshes.Where(t => t.Name.Equals(s)).Count() > 0)
                {
                    ModelMesh modelMeshChar = this.textModel.Meshes[s];
                    foreach (BasicEffect effect in modelMeshChar.Effects)
                    {
                        effect.EnableDefaultLighting();
                        effect.World = ((Char.IsUpper(c)) ? Matrix.Identity : Matrix.CreateScale(0.6F)) * Matrix.CreateRotationX(MathHelper.ToRadians(90)) * Matrix.CreateTranslation(position) * Matrix.CreateRotationY(MathHelper.ToRadians(-camera.RotationY));
                        effect.View = camera.Rotation * camera.View;
                        effect.Projection = camera.Projection;
                        effect.DiffuseColor = color;
                        effect.LightingEnabled = true;

                    }
                    modelMeshChar.Draw();
                }
                if (l + 1 < textArray.Length && textArray[l + 1] == ' ')
                    position.X += 0.10F;
                else if (l + 1 < textArray.Length && Char.IsUpper(textArray[l + 1]))
                    position.X += 0.5F;
                else if (Char.IsUpper(c) && l + 1 < textArray.Length && !Char.IsUpper(textArray[l + 1]))
                    position.X += 0.3F;
                else if (!c.ToString().Equals(""))
                    position.X += 0.2F;

            }
        }

        private bool isKeyPressed(Keys keys)
        {
            return currentKeyState[keys] == KeyState.Down && previousKeyState[keys] == KeyState.Up;
        }

        private bool isKeyPushed(Keys keys)
        {
            return currentKeyState[keys] == KeyState.Down;
        }

        private bool isKeyPressed(ButtonState button, ButtonState previousButton)
        {
            return button == ButtonState.Pressed && previousButton == ButtonState.Released;
        }

        private Matrix getWorldForPieceOnTile(Tile tile)
        {
            return getWorldForPieceOnTile(tile, null);
        }

        private Matrix getWorldForPieceOnTile(Tile tile, GameTime gameTime)
        {
            Matrix unmarkedPieceCorrection = (tile.Piece.State == EState.Unmarked ? Matrix.CreateTranslation(0, -PIECE_HEIGHT, 0) * Matrix.CreateRotationX(MathHelper.Pi) : Matrix.Identity);
            Matrix world = unmarkedPieceCorrection;

            if (gameTime != null)
                world *= animate(gameTime, tile.Location);

            world *= getTranslationMatrix(tile) * Matrix.CreateTranslation(0, TILE_HEIGHT, 0);
            return world;
        }

        private void DrawCharOnPoint(ModelMesh modelMeshChar, Vector3 target, Vector3 color)
        {
            foreach (BasicEffect effect in modelMeshChar.Effects)
            {
                effect.EnableDefaultLighting();

                effect.World = Matrix.CreateRotationY(MathHelper.ToRadians(-camera.RotationY)) * Matrix.CreateTranslation(target);
                effect.View = camera.Rotation * camera.View;
                effect.Projection = camera.Projection;
                //effect.TextureEnabled = true;
                //effect.Texture = tileTexture;
                effect.DiffuseColor = color;
            }
            modelMeshChar.Draw();
        }

        private void resetAfterSpriteBatch()
        {
            GraphicsDevice.RenderState.DepthBufferEnable = true;
            GraphicsDevice.RenderState.AlphaBlendEnable = false;
            GraphicsDevice.RenderState.AlphaTestEnable = false;
        }

        private Matrix getTranslationMatrix(Tile tile)
        {
            return getTranslationMatrix(tile.Location);
        }
        private Matrix getTranslationMatrix(BoardPoint location)
        {
            BoardPoint topLeftBoundary = game.GameState.Board.BoardArray.TopLeftBoundary;
            BoardPoint bottomRightBoundary = game.GameState.Board.BoardArray.BottomRightBoundary;

            float xPosition = ((-topLeftBoundary.X - (bottomRightBoundary.X - topLeftBoundary.X) / 2f) + location.X);
            float zPosition = ((-topLeftBoundary.Y - (bottomRightBoundary.Y - topLeftBoundary.Y) / 2f) + location.Y);
            return Matrix.CreateTranslation(xPosition, 0, zPosition);
        }

        private Vector3 getTranslationVector(int x, int y, int height)
        {
            BoardPoint origin = game.GameState.Board.BoardArray.TopLeftBoundary;
            BoardPoint bottomRightBoundary = game.GameState.Board.BoardArray.BottomRightBoundary;

            float xPosition = ((-origin.X - (bottomRightBoundary.X - origin.X) / 2f) + x);
            float zPosition = ((-origin.Y - (bottomRightBoundary.Y - origin.Y) / 2f) + x);
            return new Vector3(xPosition, height, zPosition);
        }

        private Tile FirstIntersectionWith()
        {
            Vector3 nearsource = new Vector3(Mouse.GetState().X, Mouse.GetState().Y, 0f);
            Vector3 farsource = new Vector3(Mouse.GetState().X, Mouse.GetState().Y, 1f);
            Matrix world = Matrix.CreateTranslation(0, 0, 0);

            Vector3 nearPoint = GraphicsDevice.Viewport.Unproject(nearsource, camera.Projection, camera.Rotation * camera.View, world);
            Vector3 farPoint = GraphicsDevice.Viewport.Unproject(farsource, camera.Projection, camera.Rotation * camera.View, world);

            // Create a ray from the near clip plane to the far clip plane. 
            Vector3 direction = farPoint - nearPoint;
            direction.Normalize();
            Ray mouseRay = new Ray(nearPoint, direction);

            float closestDistance = float.MaxValue;
            Tile closestTile = null;

            foreach (Tile tile in game.GameState.Board.BoardArray.Tiles)
            {
                BoundingBox box = getTranslatedBoundingBox(getTranslationMatrix(tile));

                // Tests with the first triangle possible.   
                float? result = mouseRay.Intersects(box);

                if (result.HasValue && result.Value < closestDistance)
                {
                    closestDistance = result.Value;
                    closestTile = tile;
                }

                if (tile.Piece == null)
                    continue;

                box = getTranslatedBoundingBox(getWorldForPieceOnTile(tile));

                // Tests with the first triangle possible.   
                result = mouseRay.Intersects(box);

                if (result.HasValue && result.Value < closestDistance)
                {
                    closestDistance = result.Value;
                    closestTile = tile;
                }

            }
            return closestTile;
        }

        private Action handledMouseAction = null;
        private BoardPoint FirstIntersectionWithNullTiles()
        {
            Vector3 nearsource = new Vector3(Mouse.GetState().X, Mouse.GetState().Y, 0f);
            Vector3 farsource = new Vector3(Mouse.GetState().X, Mouse.GetState().Y, 1f);
            Matrix world = Matrix.CreateTranslation(0, 0, 0);

            Vector3 nearPoint = GraphicsDevice.Viewport.Unproject(nearsource, camera.Projection, camera.Rotation * camera.View, world);
            Vector3 farPoint = GraphicsDevice.Viewport.Unproject(farsource, camera.Projection, camera.Rotation * camera.View, world);

            // Create a ray from the near clip plane to the far clip plane. 
            Vector3 direction = farPoint - nearPoint;
            direction.Normalize();
            Ray mouseRay = new Ray(nearPoint, direction);

            float closestDistance = float.MaxValue;
            BoardPoint closestPoint = default(BoardPoint);


            foreach (BoardPoint bp in listBoardPoints)
            {
                BoundingBox box = getTranslatedBoundingBox(getTranslationMatrix(bp));

                // Tests with the first triangle possible.   
                float? result = mouseRay.Intersects(box);

                if (result.HasValue && result.Value < closestDistance)
                {
                    closestDistance = result.Value;
                    closestPoint = bp;
                }
            }
            return closestPoint;
        }

        private BoundingBox getTranslatedBoundingBox(Matrix matrix)
        {
            Matrix translationMatrix = matrix;
            BoundingBox boundingBox = tileModel.BoundingBox;
            Vector3 min = Vector3.Transform(boundingBox.Min, translationMatrix);
            Vector3 max = Vector3.Transform(boundingBox.Max, translationMatrix);

            return new BoundingBox(min, max);
        }

        private Tile selectedPiece;
        private BoardPoint selectedBoardPoint;
        private bool MouseIsActive = true;
        private bool GameIsOver = false;
        private string winMessage;
        private Vector3 winMessageColor;

        private void handleMouseClick(GameTime gameTime)
        {
            if (MouseIsActive && !GameIsOver)
            {
                bool? b = null;
                Tile t = SelectedTile;

                // not enough pieces to move, place new
                if (game.GameState.Board.BoardArray.Pieces.Count(p => p != null) < 12)
                {
                    if (t != null)
                        handledMouseAction = new Action(SelectedTile.Location, game.ColorToPlay);
                }
                // move pieces, enough pieces.
                else
                {
                    // is there a tile w/ piece selected
                    if (selectedPiece != null)
                    {
                        if (t == null)
                        {
                            selectedBoardPoint = SelectedBoardpoint;
                        }
                        else
                        {
                            if (t.Piece == null)
                            {

                                if (selectedBoardPoint.Set)
                                {
                                    handledMouseAction = new Action(t.Location, selectedPiece.Location, selectedBoardPoint);


                                    selectedPiece = null;
                                    SelectedTile = null;
                                    selectedBoardPoint = default(BoardPoint);
                                    SelectedBoardpoint = default(BoardPoint);
                                }
                                else
                                {
                                    handledMouseAction = new Action(selectedPiece.Location, t.Location);

                                    selectedPiece = null;
                                    SelectedTile = null;
                                }
                            }
                        }
                    }

                    else
                    {
                        if (t != null && t.Piece != null && t.Piece.Color == game.ColorToPlay)
                            selectedPiece = t;
                    }
                }

                if (handledMouseAction != null)
                {
                    bool twelvepieces = game.GameState.Board.BoardArray.Pieces.Count(p => p != null) == 12;

                    if (handledMouseAction.AddingPiece ^ twelvepieces)
                        b = game.GameState.PerformAction(handledMouseAction);
                }

                if (b.HasValue && b.Value)
                {
                    animationInProgress = true;
                    animationStart = gameTime.TotalGameTime.TotalMilliseconds;
                }

                if (handledMouseAction != null && false == animationInProgress)
                {
                    resetMouseHandlingVariables();
                }

                if (b.HasValue && b.Value == false)
                    error.Play();
            }
        }

        private void resetMouseHandlingVariables()
        {
            handledMouseAction = null;
            selectedPiece = null;
            SelectedTile = null;
            selectedBoardPoint = default(BoardPoint);
            SelectedBoardpoint = default(BoardPoint);
        }

        private static Vector3 getColor(EColor c)
        {
            switch (c)
            {
                case EColor.Red:
                    return Color.Red.ToVector3();
                case EColor.White:
                    return Color.WhiteSmoke.ToVector3();
                default:
                    throw new ArgumentException("Should not happen", "c");
            }
        }


        private BoundingBoxModel getPieceModel(EColor c)
        {
            switch (c)
            {
                case EColor.Red:
                    return pawnRedModel;
                case EColor.White:
                    return pawnWhiteModel;
                default:
                    throw new ArgumentException("Should not happen", "c");
            }
        }

        private bool animationInProgress;
        private double animationStart;
        private Matrix animate(GameTime gameTime, BoardPoint newPiecePos)
        {
            Matrix result = Matrix.Identity;

            if (handledMouseAction != null && animationInProgress && handledMouseAction.NewPiecePosition == newPiecePos)
            {
                float x = (float)(gameTime.TotalGameTime.TotalMilliseconds - animationStart) / 1000f;

                if (handledMouseAction.AddingPiece)
                {
                    result *= Matrix.CreateTranslation(0, 6 - (6 * x), 0);
                }
                else

                    //Moving a piece
                    if (handledMouseAction.OldPiecePosition.HasValue)
                    {
                        Vector3 direction = boardPointToVector3(handledMouseAction.NewPiecePosition) -
                                            boardPointToVector3(handledMouseAction.OldPiecePosition.Value);

                        if (handledMouseAction.JumpingPiece)
                        {
                            result *= Matrix.CreateTranslation(0, -PIECE_HEIGHT, 0);
                            float radians = MathHelper.Pi * (1-x);
                            //Console.WriteLine("Rotation: {0,3} = {1,3}", x, radians);
                            result *= Matrix.CreateRotationX(radians);
                            result *= Matrix.CreateTranslation(0, PIECE_HEIGHT, 0);

                            result *= Matrix.CreateRotationY(MathHelper.ToRadians(-rotationDegrees(handledMouseAction)));

                        }

                        result *= Matrix.CreateTranslation(-direction);
                        result *= Matrix.CreateTranslation(direction * x);


                        //Moving a piece WITH jumping
                        if (handledMouseAction.JumpingPiece)
                        {
                            float yPosition = (float)Math.Sqrt(Math.Sin(x * MathHelper.Pi));
                            Console.WriteLine("{0,3} = {1}", x, yPosition);
                            result = Matrix.CreateTranslation(0, yPosition, 0) * result;
                        }


                    }

                if (x >= 1)
                {
                    resetMouseHandlingVariables();
                    animationInProgress = false;
                }
            }
            return result;
        }

        private static Vector3 boardPointToVector3(BoardPoint bp)
        {
            return new Vector3(bp.X, 0, bp.Y);
        }

        private static float rotationDegrees(Action a)
        {
            int dx = (a.NewPiecePosition.X - a.OldPiecePosition.Value.X);
            int dy = (a.NewPiecePosition.Y - a.OldPiecePosition.Value.Y);

            if (dx == 0 && dy < 0)
                return 0;
            if (dx > 0 && dy < 0)
                return 45;
            if (dx > 0 && dy == 0)
                return 90;
            if (dx > 0 && dy > 0)
                return 135;

            if (dx == 0 && dy > 0)
                return 180;
            if (dx < 0 && dy > 0)
                return 225;
            if (dx < 0 && dy == 0)
                return 270;
            if (dx < 0 && dy < 0)
                return 315;

            return 0;
        }
    }
}
